import re
from pony import orm
from .cosine_similary import cosine_similarity_main
from ..models.db import MedicineNormalize, ChineseMedicineNormalize, MedicineCutWord


class MedicineNormalizaiton:

    def __init__(self):
        pass

    def medicine_synonym_match(self, enter):
        """
        精准匹配:药品同义词库,别称中包含注册名\同义词\注册名与商品名的拼接词,商品名中包含标准商品名
        :param enter: 输入字符串
        :return: 精准匹配结果
        """
        with orm.db_session:
            candidate_medicine_list = list(orm.select(
                medicine.nhsa_name
                for medicine in MedicineNormalize
                if
                medicine.other_name == enter
                or
                medicine.trade_name == enter
            ))

            return candidate_medicine_list

    def medicine_trade_name_contain_match(self, enter):
        """
        商品名包含匹配:
        :param enter: 输入字符串
        :return: 匹配结果
        """
        with orm.db_session:
            candidate_medicine_list = list(orm.select(
                (medicine.nhsa_name,
                 medicine.trade_name)
                for medicine in MedicineNormalize
                if
                medicine.trade_name
                and
                medicine.trade_name in enter
            ))
            candidate_medicine_list.sort(key=lambda x:len(x[1]), reverse=True)
            return candidate_medicine_list

    def chinese_medicine_synonym_match(self, enter):
        """
        精准匹配:中药的药品同义词库。
        :param enter: 输入字符串
        :return: 精准匹配结果
        """
        with orm.db_session:
            candidate_medicine_list = list(orm.select(
                medicine.name
                for medicine in ChineseMedicineNormalize
                if
                medicine.other_name == enter
            ))

            return candidate_medicine_list

    def cut_word_search_by_name(self,input_medicine):

        candidate_medicine_cutword_dict = {'name': input_medicine, 'main_constituent': [], 'dosage_form': [],
                                           'trade_name': []}
        with orm.db_session:
            candidate_medicine_cutword_list = list(orm.select(
                (medicine.name,
                 medicine.cut_word_name,
                 medicine.cut_word_property)
                for medicine in MedicineCutWord
                if
                medicine.name == input_medicine
            ))
            for data in candidate_medicine_cutword_list:
                if '主成分' in data[2]:
                    candidate_medicine_cutword_dict['main_constituent'].append(data[1])
                elif '商品名' in data[2]:
                    candidate_medicine_cutword_dict['trade_name'].append(data[1])
                elif '剂型' in data[2]:
                    candidate_medicine_cutword_dict['dosage_form'].append(data[1])
        # 对查出来的属性,其列表按字符长度从大到小排序
        candidate_medicine_cutword_dict['main_constituent'].sort(key=lambda x: len(x), reverse=True)
        candidate_medicine_cutword_dict['trade_name'].sort(key=lambda x: len(x), reverse=True)
        candidate_medicine_cutword_dict['dosage_form'].sort(key=lambda x: len(x), reverse=True)
        return candidate_medicine_cutword_dict

    def cut_word_match(self, input_medicine):
        """
        切词匹配:查询输入的药品包含哪些切词以及该切词是属于那种类型。
        :param enter: 输入字符串
        :return: 匹配结果
        """
        with orm.db_session:
            candidate_medicine_list = []
            # # 主成分匹配,查找包含主成分的候选值
            candidate_medicine_name_list = list(orm.distinct(
                medicine.name
            for medicine in MedicineCutWord
                if
                medicine.cut_word_name in input_medicine
                and
                '主成分' in medicine.cut_word_property
            ))
            # 如果通过主成分能够找到一些候选项,查看这些候选项中的其他属性
            if candidate_medicine_name_list:
                for candidate_medicine_name in candidate_medicine_name_list:
                    candidate_medicine_cutword_dict = self.cut_word_search_by_name(candidate_medicine_name)
                    candidate_medicine_cutword_dict['matched_main_constituent'] = ''
                    candidate_medicine_cutword_dict['matched_dosage_form'] = ''
                    # 查到的属性已经按长度进行了排序,这里如果能匹配中,则一定是最长的匹配项
                    for main_constituent in candidate_medicine_cutword_dict['main_constituent']:
                        if main_constituent in input_medicine:
                            candidate_medicine_cutword_dict['matched_main_constituent'] = main_constituent
                            break
                    for dosage_form in candidate_medicine_cutword_dict['dosage_form']:
                        if dosage_form in input_medicine:
                            candidate_medicine_cutword_dict['matched_dosage_form'] = dosage_form
                            break
                    candidate_medicine_list.append(candidate_medicine_cutword_dict)
        return candidate_medicine_list

    def medicine_accurate_match(self, input_medicine, processed_medicine):
        # 如果有录入药品名称,尝试用录入药品精准匹配同义词库和中药库,注意这里可能需要做预处理
        if input_medicine:
            normalized_result = self.medicine_synonym_match(input_medicine)
            if normalized_result:
                return normalized_result, 1
            normalized_result = self.chinese_medicine_synonym_match(input_medicine)
            if normalized_result:
                return normalized_result, 1
        # 用处理后的药品名称做同义词库和中药库的精准匹配
        normalized_result = self.medicine_synonym_match(processed_medicine)
        if normalized_result:
            return normalized_result, 1
        normalized_result = self.chinese_medicine_synonym_match(processed_medicine)
        if normalized_result:
            return normalized_result, 1
        return normalized_result, 0

    def medicine_fuzzy_match(self, input_medicine):
        with orm.db_session:
            # # 主成分匹配,查找包含主成分的候选值
            chinese_candidate_medicine_list = list(orm.select(
                medicine.name
                for medicine in ChineseMedicineNormalize
            ).order_by(orm.raw_sql('SIMILARITY(medicine.other_name, $input_medicine) DESC')).limit(10))

            candidate_medicine_list = list(orm.select(
                medicine.nhsa_name
                for medicine in MedicineNormalize
            ).order_by(orm.raw_sql('SIMILARITY(medicine.other_name, $input_medicine) DESC')).limit(10))
            candidate_medicine_list = chinese_candidate_medicine_list + candidate_medicine_list
            candidate_medicine_list.sort(key=lambda x:cosine_similarity_main(x, input_medicine), reverse=True)

            return candidate_medicine_list[:10]

    def sort_by_main_constituent_and_dosage_form(self,normalized_result_list, input_medicine):
        if normalized_result_list == []:
            return []

        normalized_result_list.sort(key=lambda x: len(x['matched_main_constituent']), reverse=True)
        max_matched_main_constituent_length = len(normalized_result_list[0]['matched_main_constituent'])
        # TODO 这里调整成只取主成分匹配最长的数据
        data_list = [data for data in normalized_result_list if len(data['matched_main_constituent']) == max_matched_main_constituent_length]
        # 再按匹配剂型的长度排序,取剂型匹配最长的数据
        data_list.sort(key=lambda x: len(x['matched_dosage_form']), reverse=True)
        max_matched_dosage_form_length = len(data_list[0]['matched_dosage_form'])
        final_sort_list = [data for data in data_list if len(data['matched_dosage_form']) == max_matched_dosage_form_length ]
        final_sort_list.sort(key=lambda x: cosine_similarity_main(x['name'],input_medicine), reverse=True)

        return final_sort_list

    def sort_by_main_constituent_and_word(self,normalized_result_list, input_medicine):
        if normalized_result_list == []:
            return []

        normalized_result_list.sort(key=lambda x: len(x['matched_main_constituent']), reverse=True)
        tmp_matched_main_constituent_length = -1
        tmp_sorted_list = []
        index = -1
        for normalized_result in normalized_result_list:
            matched_main_constituent_length = len(normalized_result['matched_main_constituent'])

            if not matched_main_constituent_length == tmp_matched_main_constituent_length:
                index += 1
                tmp_sorted_list.append([])
                tmp_matched_main_constituent_length =matched_main_constituent_length
                tmp_sorted_list[index].append(normalized_result)

            else:
                tmp_sorted_list[index].append(normalized_result)

        final_sort_list = []
        for data_list in tmp_sorted_list:
            data_list.sort(key=lambda x:cosine_similarity_main(x['name'],input_medicine), reverse=True)
            for data in data_list:
                final_sort_list.append(data)

        return final_sort_list

    def normalize(self, input_medicine):
        '''
        做精准匹配时,输入为录入值和后处理之后的费用名称,其中录入值可能没有,
        做推荐时,使用的输入为经过后处理之后的费用名称
        '''
        normalized_result_list = []
        normalized_result_list_trade_name_in_bracket = []
        normalized_result_list_contain_main_constituent = []
        normalized_result_list_contain_dosage_form = []
        normalized_result_list_not_contain_dosage_form = []

        trade_name_contain_match_result_list = self.medicine_trade_name_contain_match(input_medicine)
        if trade_name_contain_match_result_list:
            # 若药品名称中存在括号，将括号里的内容取出来,[],(),（）,{},【】,<>
            medicine_in_bracket = re.findall("\[(.*?)]|\((.*?)\)|（(.*?)）|{(.*?)}|【(.*?)】|<(.*?)>", input_medicine)
            medicine_in_bracket = ["".join(line) for line in medicine_in_bracket]

            for trade_name_contain_match_result in trade_name_contain_match_result_list:
                data = self.cut_word_search_by_name(trade_name_contain_match_result[0])
                data['matched_trade_name'] = trade_name_contain_match_result[1]
                normalized_result_list.append(data)
                # 括号内的词条与查询到的商品名一致,优先级提高
                if trade_name_contain_match_result[1] in medicine_in_bracket:
                    normalized_result_list_trade_name_in_bracket.append(data)
            # 如果存在 括号内的词条与查询到的商品名一致的情况,则使用这些作为候选项
            if normalized_result_list_trade_name_in_bracket:
                normalized_result_list = normalized_result_list_trade_name_in_bracket

            # TODO 如果输入词条与通过商品名匹配到的词条中主成分\剂型都没匹配中, 可否认为这个输入词条只包含商品名, 此时按相似度进行输出
            for normalized_result in normalized_result_list:
                normalized_result['matched_main_constituent'] = ''
                normalized_result['matched_dosage_form'] = ''
                normalized_result['matched_trade_name'] = ''
                # 查到的属性已经按长度进行了排序,这里如果能匹配中,则一定是最长的匹配项
                for main_constituent in normalized_result['main_constituent']:
                    if main_constituent in input_medicine:
                        normalized_result['matched_main_constituent'] = main_constituent
                        break
                for dosage_form in normalized_result['dosage_form']:
                    if dosage_form in input_medicine:
                        normalized_result['matched_dosage_form'] = dosage_form
                        break

                if normalized_result['matched_main_constituent']:
                    normalized_result_list_contain_main_constituent.append(normalized_result)
                    if normalized_result['matched_dosage_form']:
                        normalized_result_list_contain_dosage_form.append(normalized_result)
                    else:
                        normalized_result_list_not_contain_dosage_form.append(normalized_result)

            # 通过商品名找到的注册名,一个主成分都没有匹配中,按相似度排名
            if normalized_result_list_contain_main_constituent ==[]:
                normalized_result_list.sort(key=lambda x: cosine_similarity_main(x['matched_trade_name'], input_medicine), reverse=True)
                return [data['name'] for data in normalized_result_list], 0
            else:
                # 剔除没有主成分匹配的词条后,如果主成分和剂型都匹配中了,则按主成分长度排序,如果只有部分匹配中了剂型,匹配中剂型的候选项为第一优先
                # A主成分 B 剂型 排序方案 A*+B* >A*+B> A+B*> A+B > A* >A
                if normalized_result_list_contain_dosage_form:
                    normalized_result_list_contain_dosage_form_soretd= self.sort_by_main_constituent_and_dosage_form(normalized_result_list_contain_dosage_form, input_medicine)
                    # 这个列表的每一个匹配项都是匹配中了主成分和剂型的,因此,只要不为空,直接输出第一个,且可自动通过
                    return [normalized_result_list_contain_dosage_form_soretd[0]['name']], 1
                # 这个列表是都只匹配到了主成分,没有匹配到剂型,直接按主成分的长度排序,主成分长度一致,再按词条相似度排序
                normalized_result_list_not_contain_dosage_form_soretd= self.sort_by_main_constituent_and_word(normalized_result_list_not_contain_dosage_form, input_medicine)
                return [data['name'] for data in normalized_result_list_not_contain_dosage_form_soretd], 0
        # 在满足主成分匹配中的情况下,提取输入药品的各类切词属性,注意这里的输入词条为后处理之后的词条
        normalized_result_list = self.cut_word_match(input_medicine)
        if normalized_result_list:
            # 查看在主成分匹配中的情况下是否存在剂型匹配,如果有,则筛选出来,如果没有,则所有的候选词条
            for normalized_result in normalized_result_list:
                if normalized_result['matched_dosage_form']:
                    normalized_result_list_contain_dosage_form.append(normalized_result)
                else:
                    normalized_result_list_not_contain_dosage_form.append(normalized_result)
            # A主成分 B 剂型 排序方案 A*+B* >A*+B> A+B*> A+B > A* >A
            if normalized_result_list_contain_dosage_form:
                normalized_result_list_contain_dosage_form_soretd = self.sort_by_main_constituent_and_dosage_form(normalized_result_list_contain_dosage_form, input_medicine)
                # 这个列表的每一个匹配项都是匹配中了主成分和剂型的,因此,只要不为空,直接输出第一个,且可自动通过
                return [normalized_result_list_contain_dosage_form_soretd[0]], 1
            # 这个列表是都只匹配到了主成分,没有匹配到剂型,直接按主成分的长度排序,主成分长度一致,再按词条相似度排序
            normalized_result_list_not_contain_dosage_form_soretd = self.sort_by_main_constituent_and_word(normalized_result_list_not_contain_dosage_form, input_medicine)
            return [data['name'] for data in normalized_result_list_not_contain_dosage_form_soretd], 0
        else:
            # 一个主成分都没匹配中,直接输入词条与注册名按相似度排序
            normalized_result_list = self.medicine_fuzzy_match(input_medicine)
            return normalized_result_list, 0